//
//  STRequest.m
//  STQuickKit
//
//  Created by yls on 13-11-11.
// Copyright (c) 2013 yls. All rights reserved.
//
// Version 1.0
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#import "STRequestEngine.h"
#import "MKNetworkEngine.h"
#import "XMLDictionary.h"

#define BoundaryConstant @"1"

#define kSTRequestStateStart    1
#define kSTRequestStateFinished 2
#define kSTRequestStateFailed   3

@interface STRequestEngine(/* privite */)

@property (nonatomic, strong) NSMutableDictionary *states;
@property (nonatomic, strong) NSString *hostName;

@end

@implementation STRequestEngine

- (id)init
{
    self = [super init];
    if (self) {
        _states = [NSMutableDictionary dictionaryWithCapacity:0];
        [self registerOperationSubclass:[STRequest class]];
    }
    return self;
}

- (id) initWithHostName:(NSString*) hostName
{
    _hostName = hostName;
    _states = [NSMutableDictionary dictionaryWithCapacity:0];
    return [super initWithHostName:hostName];
}

- (STRequest *)sendAsynchronousRequestWithPath:(NSString *)path
                                        params:(NSDictionary *)params
                                        method:(STRequestMethod)method
                             completionHandler:(STRequestCompletion)completionHandler
{
    NSString *httpMethod = method == STRequestMethodGet ? @"GET" : @"POST";
    [super registerOperationSubclass:[STRequest class]];
    STRequest *op = (STRequest *)[self operationWithPath:path params:params httpMethod:httpMethod];
    
    __block MKNetworkOperation *op_b = op;
    
    [_states setObject:@(kSTRequestStateStart) forKey:op.url];
    
    [op addCompletionHandler:^(MKNetworkOperation *completedOperation) {
        [_states setObject:@(kSTRequestStateFinished) forKey:op_b.url];
        
        NSString *respStr = completedOperation.responseString;
        id respData;
        
        BOOL rc = [NSJSONSerialization isValidJSONObject:completedOperation.responseData];
        if (rc) {
            respData = completedOperation.responseJSON;
        } else {
            NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:completedOperation.responseData];
            if (xmlParser) {
                NSDictionary *dict = [NSDictionary dictionaryWithXMLData:completedOperation.responseData];
                if ([dict isKindOfClass:[NSDictionary class]]) {
                    respData = dict;
                }
            }
        }
        
        if (!respData) {
            respData = completedOperation.responseData;
        }
        completionHandler(respStr, respData, nil);
        
        [self postRequestEngineEnd];
    } errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) {
        completionHandler(nil, nil, error);
        [_states setObject:@(kSTRequestStateFailed) forKey:op_b.url];
        
        [self postRequestEngineEnd];
    }];
    [self enqueueOperation:op];

    return op;
}

- (void)postRequestEngineEnd
{
    NSInteger state = [self stRequestEngineState];
    if (state == kSTRequestStateFailed) {
        NSDictionary *userInfo = @{@"state": @(kSTRequestStateFailed)};
        [[NSNotificationCenter defaultCenter] postNotificationName:self.notificationNameState object:nil userInfo:userInfo];
    }
    if (state == kSTRequestStateFinished) {
        NSDictionary *userInfo = @{@"state": @(kSTRequestStateFinished)};
        [[NSNotificationCenter defaultCenter] postNotificationName:self.notificationNameState object:nil userInfo:userInfo];
    }
}

- (NSString *)notificationNameState
{
    NSString *notiName = NSStringFromClass([self class]);
    notiName = [notiName stringByAppendingString:@"State"];
    return notiName;
}

- (NSInteger)stRequestEngineState
{
    NSInteger state = kSTRequestStateFinished;
    for (id value in _states.allValues) {
        NSInteger v = [value integerValue];
        if (v == kSTRequestStateFailed) {
            state = kSTRequestStateFailed;
            break;
        } else if (v == kSTRequestStateStart) {
            state = kSTRequestStateStart;
        }
    }
    return state;
}

- (NSDictionary *)sendSynchronousRequestWithPath:(NSString *)path
                                          params:(NSDictionary *)params
                                          method:(STRequestMethod)method;
{
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    NSMutableString *urlString;
    if (params) {
        NSMutableString *paramsStr = [NSMutableString stringWithCapacity:0];
        NSMutableArray *paramsArr = [NSMutableArray arrayWithCapacity:0];
        for (NSString *key in params) {
            id value = params[key];
            NSString *param = [NSString stringWithFormat:@"%@=%@", key, value];
            [paramsArr addObject:param];
        }
        [paramsStr appendString:[paramsArr componentsJoinedByString:@"&"]];
        
        if (method == STRequestMethodPost) {
            NSData *paramsData = [paramsStr dataUsingEncoding:NSUTF8StringEncoding];
            [request setHTTPBody:paramsData];
        }
        
//        NSString *url = [NSString stringWithFormat:@"http://%@", self.];
        NSString *useSSL;
        urlString = [NSMutableString stringWithFormat:@"%@://%@", useSSL ? @"https" : @"http", self.hostName];
        
        if(self.portNumber != 0)
            [urlString appendFormat:@":%d", self.portNumber];
        
        if(self.apiPath)
            [urlString appendFormat:@"/%@", self.apiPath];
        
        if(![path isEqualToString:@"/"]) { // fetch for root?
            
            if(path.length > 0 && [path characterAtIndex:0] == '/') // if user passes /, don't prefix a slash
                [urlString appendFormat:@"%@", path];
            else if (path != nil)
                [urlString appendFormat:@"/%@", path];
        }
        
        if (method == STRequestMethodGet) {
            if ([urlString rangeOfString:@"?"].location != NSNotFound) {
                [urlString appendFormat:@"&%@", paramsStr];
            } else {
                [urlString appendFormat:@"?%@", paramsStr];
            }
        }
    }
    
    urlString = [NSMutableString stringWithString:[urlString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    
    [request setURL:[NSURL URLWithString:urlString]];
    
    NSString *methodStr = [STRequestEngine requestMethodName:method];
    [request setHTTPMethod:methodStr];
    
    NSDictionary *retDict;
    NSError *error;
    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error];
    if (!error) {
        retDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
        if (error) {
            NSLog(@"%@", error.localizedDescription);
        } else {
            return retDict;
        }
    } else {
        NSLog(@"%@", error.localizedDescription);
    }
    return nil;
}

+ (NSString *)requestMethodName:(STRequestMethod)method;
{
    switch (method) {
        case STRequestMethodGet:
            return @"GET";
            break;
        case STRequestMethodPost:
            return @"POST";
        default:
            break;
    }
    return @"POST";
}

- (STRequest *)uploadImageWithUrl:(NSString *)url
                           params:(NSDictionary *)params
                        imageData:(NSData *)imageData
                        imageName:(NSString *)imageName
                  progressChanged:(STRequestDownloadProgress)progressChanged
                completionHandler:(STRequestCompletion)completionHandler;
{
    [super registerOperationSubclass:[STRequest class]];
    STRequest *op = (STRequest *)[self operationWithPath:url
                                              params:params
                                          httpMethod:@"POST"];
    
    [op addData:imageData forKey:imageName];
    // setFreezable uploads your images after connection is restored!
    [op setFreezable:YES];
    
    [op onUploadProgressChanged:^(double progress) {
        progressChanged(progress);
    }];
    
    [op addCompletionHandler:^(MKNetworkOperation* completedOperation) {
        NSString *respStr = completedOperation.responseString;
        
        id respData;
        
        BOOL rc = [NSJSONSerialization isValidJSONObject:completedOperation.responseData];
        if (rc) {
            respData = completedOperation.responseJSON;
        } else {
            NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:completedOperation.responseData];
            if (xmlParser.parse) {
                NSDictionary *dict = [NSDictionary dictionaryWithXMLData:completedOperation.responseData];
                if ([dict isKindOfClass:[NSDictionary class]]) {
                    respData = dict;
                }
            }
        }
        
        if (!respData) {
            respData = completedOperation.responseData;
        }
        
        completionHandler(respStr, respData, nil);
    } errorHandler:^(MKNetworkOperation *errorOp, NSError* error) {
        completionHandler(nil, nil, error);
    }];
    
    [self enqueueOperation:op];
    
    return op;
}

- (STRequest *) downloadFileFrom:(NSString*) remoteURL
                          toFile:(NSString*) filePath
                 progressChanged:(STRequestDownloadProgress)progressChanged
               completionHandler:(STRequestCompletion)completionHandler;
{
    [super registerOperationSubclass:[STRequest class]];
    STRequest *op = (STRequest *)[self operationWithURLString:remoteURL];
    
    [op addDownloadStream:[NSOutputStream outputStreamToFileAtPath:filePath
                                                            append:YES]];

    NSMutableDictionary *newHeadersDict = [[NSMutableDictionary alloc] init];
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    if ([fileManager fileExistsAtPath:filePath])
    {
        NSError *error = nil;
        unsigned long long fileSize = [[fileManager attributesOfItemAtPath:filePath error:&error] fileSize];
        
        NSString *headerRange = [NSString stringWithFormat:@"bytes=%llu-", fileSize];
        [newHeadersDict setObject:headerRange forKey:@"Range"];
    }
    [op addHeaders:newHeadersDict];
    
    [op onDownloadProgressChanged:^(double progress) {
        progressChanged(progress);
    }];
    
    [op addCompletionHandler:^(MKNetworkOperation* completedOperation) {
        NSString *respStr = completedOperation.responseString;
        
        id respData;
        
        BOOL rc = [NSJSONSerialization isValidJSONObject:completedOperation.responseData];
        if (rc) {
            respData = completedOperation.responseJSON;
        } else {
            NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:completedOperation.responseData];
            if (xmlParser) {
                NSDictionary *dict = [NSDictionary dictionaryWithXMLData:completedOperation.responseData];
                if ([dict isKindOfClass:[NSDictionary class]]) {
                    respData = dict;
                }
            }
        }
        
        if (!respData) {
            respData = completedOperation.responseData;
        }
        
        completionHandler(respStr, respData, nil);
    } errorHandler:^(MKNetworkOperation *errorOp, NSError* error) {
        completionHandler(nil, nil, error);
    }];
    
    [self enqueueOperation:op];
    
    return op;
}

@end
